// @ts-nocheck
// 获取元素
const btn = document.getElementById("submit");
const uploadFile = document.getElementById("uploadFile");
const pause = document.getElementById("pause");

// 定义分片大小
const CHUNK_SIZE = 1024 * 1024 * 5; // 5MB
// 获取最大线程数量
const THREAD_COUNT = navigator.hardwareConcurrency || 4; // 默认4个线程
// 定义最大连接数量
const MAX_CONCURRENT = 5;
// 定义当前请求数量
let CUR_CONCURRENT = 0;

// 定义当前上传文件的信息
let uploadData = {
	file: null,
	ext: "",
	key: "",
	preFlag: false,
	chunkArr: [],
	hasResolve: [],
};
// 注册事件
btn.addEventListener("click", handler);
uploadFile.addEventListener("change", fileChange);
// 点击事件回调
async function handler() {
	// 如果未选中文件，则中断函数
	if (!uploadData.preFlag) {
		console.log("未选择文件或者文件尚未做好上传准备");
		return;
	}
	btn.textContent = "暂停";
	// 2. 获取文件分片信息
	const chunkArr = uploadData.chunkArr;

	let current = 0; // 当前的请求块
	const request = () => {
		while (CUR_CONCURRENT < MAX_CONCURRENT && current < chunkArr.length) {
			// 获取对应分块
			const chunk = chunkArr[current++];
			// 5. 在postFile方法中，将数据封装到formData中再进行传输
			postFile(chunk).then(() => {
				console.log(`第${chunk.index}块分片上传完毕`);
				// 如果数量依旧大于零
				if (current < chunkArr.length) {
					request();
				}
			});
		}
	};
	// 4. 开始上传
	return request();
}

// 选中文件时的回调
async function fileChange(e) {
	// 重置uploadData
	uploadData = {
		file: null, // 选中的文件对象
		ext: "", // 文件的后缀名
		key: "", // 文件的key
		preFlag: false, // 文件是否允许上传
		chunkArr: [], // 文件分块后的数组
		hasResolve: [], // 数组内容为 0 | 1，前者表示对应下标的chunk暂未传输，后者表示已经传输完成
	};
	// 获取一些基本信息
	const file = (uploadData.file = e.target.files[0]);     // 选中的文件
	const ext = (uploadData.ext = file.name.match(/\.(.+)$/)[1]); // 获取文件后缀
	const key = (uploadData.key =
		"id-" + Math.random().toString(36).substring(2, 11)); // 生成唯一id

	// 对文件进行分片
	const preTime = new Date();
	const result = await cutFile(file);
	const nowTime = new Date();
	console.log(`耗时${nowTime - preTime}毫秒`);

	console.log(result);
	// 赋值给uploadData
	uploadData.chunkArr = result.chunkArr;
	uploadData.hasResolve = new Array(result.length).fill(0);
	// 告知服务器做好准备，提前创建好空文件
	return fetch("/preUpload", {
		method: "POST",
		headers: {
			"Content-type": "application/json",
		},
		body: JSON.stringify({
			key,
			ext,
			length: result.length,
		}),
	})
		.then(interceptor)
		.then((res) => {
			console.log("服务器响应：", res.data);
			uploadData.preFlag = true;
		});
}

// 文件上传接口
function postFile(chunk) {
	const formData = new FormData();
	formData.append("file", chunk.blob);
	formData.append("index", chunk.index);
	formData.append("start", chunk.start);
	formData.append("end", chunk.end);
	formData.append("hash", chunk.hash);
	formData.append("key", uploadData.key);

	// 定义循环体
	const request = () => {
		// 发送请求前将请求数量自增
		CUR_CONCURRENT++;
		return fetch("/uploadFile", {
			method: "POST",
			body: formData,
		})
			.then((res) => {
				// 请求结果回来后将请求数量减少
				CUR_CONCURRENT--;
				return interceptor(res);
			})
			.catch((error) => {
				return Promise.reject(error);
			});
	};

	return request();
}

// 文件分片函数，返回一个分片后的数组
function cutFile(file) {
	// 需要产生多少个分块
	const chunkCount = Math.ceil(file.size / CHUNK_SIZE);
	// 每个进程需要多少个
	const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT);
	// 结果数组
	const result = [];

	let nums = 0;
	const { promise, resolve } = Promise.withResolvers();

	// 进入循环，给每个线程分配任务，同时判断当分块数量小于线程数量的时候，就终止
	for (
		let i = 0;
		i < THREAD_COUNT && i * threadChunkCount < chunkCount;
		i++
	) {
		const start = i * threadChunkCount;
		const end = Math.min(chunkCount, (i + 1) * threadChunkCount);
		const worker = new Worker("/static/js/worker.js");
		worker.postMessage({
			file: uploadData.file,
			size: CHUNK_SIZE,
			startIndex: start,
			endIndex: end,
		});
		worker.addEventListener("message", (event) => {
			const { data } = event;
			for (let i = start; i < end; i++) {
				result[i] = data[i - start];
				nums++;
			}
			// 记得关闭线程
			worker.terminate();
			// 判断是否分片完毕
			if (nums === chunkCount) {
				resolve({
					chunkArr: result,
					length: result.length,
				});
			}
		});
	}
	return promise;
}

// 定义拦截器，对fetch的结果进行解析
async function interceptor(res) {
	const reader = await res.body.getReader();

	// 在这里整合数据
	let data = { done: false, value: [] };
	let value = [];
	while (data.done !== true) {
		data = await reader.read();
		if (data.done) {
			break;
		}
		value.push(...data.value);
	}
	// 将二进制流转成字符串
	let obj = String.fromCharCode(...value);
	obj = JSON.parse(obj);
	// 如果后端校验失败
	if (obj.data !== "ok") {
		return Promise.reject("fail");
	}
	return Promise.resolve(obj);
}
